#include "facade.h"

facade* facade::m_instance = nullptr;

facade::facade()
{
}

facade::~facade()
{
	m_ctx_pool.reset();
	for (auto client : m_clients) {
		delete client.second;
	}
	m_clients.clear();
}

facade& facade::get_instance()
{
	if (m_instance == nullptr)
	{
		m_instance = new facade();
	}
	return *m_instance;
}

void facade::regist_on_client_connect(connect_handle on_connect)
{
	m_event_despatcher.regist_connect_event(EventKey::on_client_connect, on_connect);
}

void facade::regist_on_server_connect(connect_handle on_connect)
{
	m_event_despatcher.regist_connect_event(EventKey::on_server_connect, on_connect);
}

void facade::regist_on_client_disconnect(connect_handle on_disconnect)
{
	m_event_despatcher.regist_connect_event(EventKey::on_client_disconnect, on_disconnect);
}

void facade::regist_on_server_disconnect(connect_handle on_disconnect)
{
	m_event_despatcher.regist_connect_event(EventKey::on_server_disconnect, on_disconnect);
}

void facade::regist_on_client_message(message_handle on_message)
{
	m_event_despatcher.regist_message_event(EventKey::on_client_message, on_message);
}

void facade::regist_on_server_message(message_handle on_message)
{
	m_event_despatcher.regist_message_event(EventKey::on_server_message, on_message);
}

void facade::regist_on_client_error(error_handle on_err)
{
	m_event_despatcher.regist_error_event(EventKey::on_client_error, on_err);
}

void facade::regist_on_server_error(error_handle on_err)
{
	m_event_despatcher.regist_error_event(EventKey::on_server_error, on_err);
}

void facade::set_remote(std::string ip, int port)
{
	m_remote_ip = ip;
	m_remote_port = port;
}

bool facade::bind_to(int port)
{
	if (m_server != nullptr) {
		m_server->stop();
		delete m_server;
	}
	try
	{
		asio::io_context& context = m_ctx_pool.get_one_context();
		m_server = new tcp_server(context, port);
		m_server->set_on_connect(std::bind(&facade::on_client_connect, this, std::placeholders::_1));
		m_server->set_on_disconnect(std::bind(&facade::on_client_disconnect, this, std::placeholders::_1));
		m_server->set_on_message(std::bind<int>(&facade::on_client_message, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
		m_server->set_on_error(std::bind(&facade::on_client_error, this, std::placeholders::_1, std::placeholders::_2));
		m_server->accept();
		return true;
	}
	catch (std::exception e)
	{
		return false;
	}
}

bool facade::send_to_client(int id, const char* data, int len)
{
	IdGroup* group = find_by_id(id);
	if (group && m_server)
	{
		net_tcp::shared_session* sess = m_server->find_session(group->cid);
		if (sess) {
			return (*sess)->send(data, len);
		}
	}
	
	return false;
}

bool facade::send_to_server(int id, const char* data, int len)
{
	IdGroup* group = find_by_id(id);
	if (group)
	{
		auto iter = m_clients.find(group->sid);
		if (iter != m_clients.end()) {
			return iter->second->send(data, len);
		}
	}
	return false;
}

bool facade::break_connetion(int id)
{
	IdGroup* group = find_by_id(id);
	if (group != nullptr)
	{
		remove_client(group->cid);
		if (m_server != nullptr) {
			m_server->remove_session(group->sid);
		}
		return true;
	}
	return false;
}

void facade::connect_toserver()
{
	asio::io_context& context = m_ctx_pool.get_one_context();
	tcp_client* client = new tcp_client(context);
	client->set_on_connect(std::bind(&facade::on_server_connect, this, std::placeholders::_1));
	client->set_on_disconnect(std::bind(&facade::on_server_disconnect, this, std::placeholders::_1));
	client->set_on_message(std::bind<int>(&facade::on_server_message, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
	client->set_on_error(std::bind(&facade::on_server_error, this, std::placeholders::_1, std::placeholders::_2));
	client->connect(m_remote_ip.c_str(), m_remote_port);
	m_clients[client->get_id()] = client;
}

void facade::remove_client(int cid)
{
	auto client_iter = m_clients.find(cid);
	if (client_iter != m_clients.end())
	{
		client_iter->second->close();
		delete client_iter->second;
		m_clients.erase(cid);
	}
}

IdGroup* facade::find_by_id(int id)
{
	IdGroup* group = nullptr;
	for (auto iter = m_tuples.begin(); iter != m_tuples.end(); iter++)
	{
		if (iter->id == id)
		{
			group = &(*iter);
			break;
		}
	}
	return group;
}


void facade::check_tuple(int id)
{
	IdGroup* group = nullptr;
	for (auto iter = m_tuples.begin(); iter != m_tuples.end(); iter++)
	{
		if (iter->id == id)
		{
			if (iter->sid == 0 && iter->cid == 0)
			{
				m_idpool.emplace_back(iter->id);
				m_tuples.erase(iter);
			}
			break;
		}
	}
}


IdGroup* facade::find_by_cid(int cid)
{
	IdGroup* group = nullptr;
	for (auto iter = m_tuples.begin(); iter != m_tuples.end(); iter++)
	{
		if (iter->cid == cid)
		{
			group = &(*iter);
			break;
		}
	}
	return group;
}

IdGroup* facade::find_by_sid(int sid)
{
	IdGroup* group = nullptr;
	for (auto iter = m_tuples.begin(); iter != m_tuples.end(); iter++)
	{
		if (iter->sid == sid)
		{
			group = &(*iter);
			break;
		}
	}
	return group;
}

int facade::allocate_id()
{
	int id = 0;
	if (m_idpool.size() > 0)
	{
		id = m_idpool.front();
		m_idpool.erase(m_idpool.begin());
	}
	else {
		id = m_tuples.size() + 1;
	}
	return id;
}

void facade::on_client_connect(int cid)
{
	IdGroup* groupptr = find_by_cid(0);
	if (groupptr == nullptr)
	{
		IdGroup group;
		group.id = allocate_id();
		group.cid = cid;
		group.sid = 0;
		m_tuples.emplace_back(std::move(group));
		groupptr = &m_tuples.back();
	}
	else {
		groupptr->cid = cid;
	}
	m_event_despatcher.trigger_connect_event(EventKey::on_client_connect, groupptr->id);
	auto iter = m_clients.find(groupptr->sid);
	if (iter == m_clients.end())
	{
		groupptr->sid = 0;
		connect_toserver();
	}
}

void facade::on_client_disconnect(int cid)
{
	IdGroup* group = find_by_cid(cid);
	if (group != nullptr)
	{
		m_event_despatcher.trigger_connect_event(EventKey::on_client_disconnect, group->id);
		group->cid = 0;
		remove_client(cid);
		check_tuple(group->id);
	}
}

int facade::on_client_message(int cid, const char* data, int len)
{
	IdGroup* group = find_by_cid(cid);
	if (group != nullptr)
	{
		if (group->sid == 0){
			connect_toserver();
		}
		return m_event_despatcher.trigger_message_event(EventKey::on_client_message, group->id, data, len);
	}
	return 0;
}

void facade::on_client_error(int cid, int error_code)
{
	IdGroup* group = find_by_cid(cid);
	if (group != nullptr)
	{
		m_event_despatcher.trigger_error_event(EventKey::on_client_error, group->id, error_code);
	}
}

void facade::on_server_connect(int sid)
{
	auto client_iter = m_clients.find(sid);
	if (client_iter != m_clients.end()) {
		IdGroup* groupptr = find_by_sid(0);
		if (groupptr == nullptr)
		{
			IdGroup group;
			group.id = allocate_id();
			group.cid = 0;
			group.sid = sid;
			m_tuples.emplace_back(std::move(group));
			groupptr = &m_tuples.back();
		}
		else {
			groupptr->sid = sid;
		}
		m_event_despatcher.trigger_connect_event(EventKey::on_server_connect, groupptr->id);
	}
}

void facade::on_server_disconnect(int sid)
{
	IdGroup* group = find_by_sid(sid);
	if (group != nullptr)
	{
		m_event_despatcher.trigger_connect_event(EventKey::on_server_disconnect, group->id);
		group->sid = 0;
		check_tuple(group->id);
	}
}

int facade::on_server_message(int sid, const char* data, int len)
{
	IdGroup* group = find_by_sid(sid);
	if (group != nullptr)
	{
		return m_event_despatcher.trigger_message_event(EventKey::on_server_message, group->id, data, len);
	}
	return 0;
}

void facade::on_server_error(int sid, int error_code)
{
	IdGroup* group = find_by_sid(sid);
	if (group != nullptr)
	{
		m_event_despatcher.trigger_error_event(EventKey::on_server_error, group->id, error_code);
	}
}
